路径引导 室外真实导航 室内模拟导航

最后更新时间:2020年12月15日

功能描述

路径引导,即导航功能,基于已有的规划路径实现导航,按照规划路径行进,辅以文字、图标引导,以及语音播报功能等。MapGIS Mobile for Android SDK提供室内外一体化的路径引导接口,并且提供模拟导航,接入定位数据后可实现真实导航。此外,还能提供丰富的指引信息,比如当前道路的名称、当前导航动作、到达终点的距离等;提供路口放大图来准确指引路线;同时支持语音播报操作,提供更加友好、科学的智能化引导服务。

主要通过API程序包com.zondy.mapgis.routeguide下的路径引导类RouteGuide的接口实现:

接口 说明
init() 初始化路径引导模块
startGNSSNavi() 开始真实导航
stopGNSSNavi() 停止真实导航
startSimNavi() 开始模拟导航
pauseOrResumeSimNavi() 暂停或继续模拟导航操作
stopSimNavi() 停止模拟导航
addGNSSLocInfo() 从应用层向中间件推送GNSS信息
registerListener() 添加一个用于接收导航相关状态的监听器
setSimNaviOptions() 设置模拟导航的控制参数

实现方法

路径引导(导航)功能实现的一般流程如下图所示:

路径引导实现流程.png

1

路径引导对象准备

构造路径引导对象RouteGuide,并通过init()方法初始化路径引导对象,设置路线对象Route、路径分析对象RouteAnalysis两个参数;

//构造路径引导对象
RouteGuide mRouteGuide = new RouteGuide();
//利用导航路径、路径分析对象来初始化路径引导对象,这两个对象在路径分析阶段即可获取
mRouteGuide.init(mRoute, mRouteAnalysis);

2

导航操作

(1)如果进行真实导航:可调用startGNSSNavi()和stopGNSSNavi()来开始、停止。

//开始真实导航
mRouteGuide.startGNSSNavi();
//停止真实导航
mRouteGuide.stopGNSSNavi();
//GPS定位监听器
private LocationListener locationListener = new LocationListener() {
    //GPS位置改变时触发
    @Override
    public void onLocationChanged(Location location) {
        //构建GNSS定位信息对象
        GNSSLocInfo gnssLocInfo = new GNSSLocInfo();
        gnssLocInfo.setLongitude(location.getLongitude());  //经度
        gnssLocInfo.setLatitude(location.getLatitude());    //纬度
        gnssLocInfo.setSpeed(location.getSpeed());          //速度
        gnssLocInfo.setAngle(location.getBearing());        //角度
        gnssLocInfo.sethDop(location.getAccuracy());        //精度
        //向RouteGuide路径引导对象中推送定位信息
        mRouteGuide.addGNSSLocInfo(gnssLocInfo);
    }
};

代码说明:要实现真实导航,必须先实现实时定位功能,然后不断推送给RouteGuide对象。实时定位功能实现,可直接调用SDK的定位接口,也可采用Android原生定位接口实现,或者利用第三方定位SDK实现(如高德定位SDK、百度定位SDK等)。不管采用哪一种方式进行定位,其接口中一定会有定位的监听器,需要在其监听器的回调函数中实例化GNSSLocInfo对象,为其设置相关必要信息,然后设置给RouteGuide对象。

(2)如果进行模拟导航:可调用startSimNavi()、pauseOrResumeSimNavi()、stopSimNavi()来开始、暂停/继续、停止模拟导航。

//开始模拟导航
mRouteGuide.startSimNavi();
//暂停模拟导航
mRouteGuide.pauseOrResumeSimNavi(true);
//继续模拟导航,暂停了之后才能继续
mRouteGuide.pauseOrResumeSimNavi(false);
//停止模拟导航
mRouteGuide.stopSimNavi();

//加速、减速:改变模拟导航参数
//创建模拟导航的参数信息(模拟速度:公里/小时;生成模拟点的频率:秒)
SimNaviOptions simNaviOptions=new SimNaviOptions(2 * 100f, 1.0f);
//设置模拟导航的控制参数
mRouteGuide.setSimNaviOptions(simNaviOptions);

代码说明:模拟导航时需要传入模拟的运动速度,以及生成模拟点的频率,内部会根据这些参数模拟运动效果。

3

导航监听

为路径引导对象设置RouteListener导航监听器,并在监听器的回调方法中获取导航指引信息,可将其以多种形式展现,如文本控件、图像视图控件等,给用户以提示。

导航,即路径引导,是一个实时、动态的过程,随着用户移动设备位置的变化,导航功能提供的信息也随之变化,所以指引信息也需要动态地获取。SDK目前没有提供默认的导航引导界面,指引信息的展示工作需要开发人员自己编码实现。SDK具有导航状态监听器,包括6个回调函数,每个函数提作用都不同,在其中提供了不同的指引信息。需要说明的是,这6个回调函数都是在子线程中执行的,如果获取信息后要操作UI界面,需要在主线程中执行。

//注册接收导航相关状态的监听器
mRouteGuide.registerListener(mRouteListener);
//实例化导航状态监听器对象
class mRouteListener implements RouteListener {
    //导航指引信息(导航路径信息,真实/模拟导航)
    @Override
    public void onRouteNaviInfo(RouteNaviInfo routenaviinfo, boolean arg1) {
        //这是最重要、使用最多的一个回调函数,可获取诸多导航指引信息,例如当前道路名称、总里程剩余值、导航主动作等等,在下方会详细介绍各种方法如何获取、使用
    }

    //真实导航状态变更回调(真实导航状态、导航状态辅助信息)
    @Override
    public void onGNSSNaviMode(GNSS_NAVI_MODE gnssNaviMode, int statusEx) {
        //可获取真实导航状态:开始、到达途经点、偏离路径、到达终点、停止。
        switch (gnssNaviMode) {
            case GNSS_NAVI_MODE_START:
                Log.e("真实导航状态:", "导航开始");
                break;
            case GNSS_NAVI_MODE_ARRIVE_MID:
                Log.e("真实导航状态:", "到达途径点");
                break;
            case GNSS_NAVI_MODE_LEEWAY:
                Log.e("真实导航状态:", "偏离路径");
                break;
            case GNSS_NAVI_MODE_STOP:
                Log.e("真实导航状态:", "导航停止");
                break;
            case GNSS_NAVI_MODE_ARRIVE_DEST:
                Log.e("真实导航状态:", "到达终点");
                break;
            default:
                break;
        }
    }

    //模拟导航状态变更回调(模拟导航状态)
    @Override
    public void onSimNaviMode(SIM_NAVI_MODE simNaviMode) {
        //可获取模拟导航状态:开始、进行中、暂停中、停止、完成。
        switch (simNaviMode) {
            case SIM_NAVI_MODE_START:
                Log.e("模拟导航状态:", "模拟导航开始");
                break;
            case SIM_NAVI_MODE_PROCESSING:
                Log.e("模拟导航状态:", "模拟导航进行中");
                break;
            case SIM_NAVI_MODE_PAUSED:
                Log.e("模拟导航状态:", "模拟导暂停中");
                break;
            case SIM_NAVI_MODE_STOP:
                Log.e("模拟导航状态:", "模拟导航停止");
                break;
            case SIM_NAVI_MODE_FINISH:
                Log.e("模拟导航状态:", "模拟导航完成");
                break;
            default:
                break;
        }
    }

    //处理定位信息
    @Override
    public void onGNSSLocInfo(GNSSLocInfo gnssLocInfo, double matchLng, double matchLat, String matchFloor, float matchAngle) {
        //可获取:卫星状态、卫星数、道路匹配后的坐标、楼层、角度等信息
    }

    //定位状态(GNSS状态)
    @Override
    public void onGNSSLocStatus(GNSS_LOC_STATUS locStatus) {
        //获取定位状态:设备是否已连接、已定位等
        switch (locStatus) {
            case GNSS_LOC_STATUS_SEARCHING:
                Log.e("定位状态:", "设备搜索中");
                break;
            case GNSS_LOC_STATUS_FIXED:
                Log.e("定位状态:", "设备已连接并已定位");
                break;
            case GNSS_LOC_STATUS_FIXING:
                Log.e("定位状态:", "设备已连接,但未定位");
                break;
            case GNSS_LOC_STATUS_DISCONNECT:
                Log.e("定位状态:", "设备不可用,例如移动端无GNSS模块,或者参数配置失败导致设备无法连接");
                break;
            default:
                break;
        }
    }

    //播报指引信息(语音文字信息、真实/模拟导航、语音信息优先级)
    @Override
    public void onPlayNaviMessage(String message, boolean gnssNavi, int enPriority) {
        //获取导航指引语音信息,如:前方200米向左行驶等等
        Log.e("播报指引信息:", message);
        //特别说明:MapGIS Mobile 10.3 for Android SDK没有提供内置的语音播报功能,用户可借助第三方开发库实现语音播报
    }
}

4

导航指引信息获取

RouteListener导航相关状态监听器中有上述6个回调函数,其中最重要、最常用的函数是导航指引信息的函数onRouteNaviInfo,从此函数中可获取到导航路径信息RouteNaviInfo对象,由此对象获取很多导航过程中非常重要的指引信息。下面将详细介绍信息如何获取与其展示方法:

(1)普通导航指引信息

普通导航指引信息:即界面上提示的当前道路名称、总路程剩余距离、当前行驶速度等信息,这些信息从RouteNaviInfo对象中获取;获取信息后进行界面展示,让用户知道当前行驶的进度与状态。

//导航指引信息回调函数
@Override
public void onRouteNaviInfo(RouteNaviInfo routenaviinfo, boolean arg1) {
    String roadName = routenaviinfo.curRoadName;          //当前道路名称
    int segDistance = routenaviinfo.segRemainDis;         //当前路段剩余距离
    int distance = routenaviinfo.remainDis;               //总里程剩余距离
    int remainTime = routenaviinfo.remainTime;            //总导航剩余时间
    float speed = routenaviinfo.speed;                    //当前行驶速度
    String a = routenaviinfo.nextRoadName;                //下一条道路的名称
}

如上述这些文字信息,开发人员可以在Activity界面上定义一些TextView文本控件,然后在回调函数中动态修改控件的文本值,即可实现提示作用。需注意这些回调函数都是在子线程中执行的,如果要修改UI控件,需返回到主线程再做操作。

基本导航指引信息.png

(2)导航动作信息

导航动作信息:即路径引导中的方向信息,告知用户该向什么方向前进,如左转弯、右转弯等。导航动作信息也是从RouteNaviInfo对象中获取,得到short类型的16进制变量,不同的值对应不同的动作,具体见如下核心代码。

//导航指引信息回调函数
@Override
public void onRouteNaviInfo(RouteNaviInfo routenaviinfo, boolean arg1) {
    //获取导航主动作
    short action = routenaviinfo.naviAction;
    //定义一个int类型变量,存储对应图片资源的ID值
    int directionId = 0;
    switch (action) {
        case 0x8:
            Log.e("导航主动作:", "直行");
            directionId = R.mipmap.direction_line;
            break;
        case 0x1:
            Log.e("导航主动作:", "左转");
            directionId = R.mipmap.direction_zz;
            break;
        case 0x2:
            Log.e("导航主动作:", "右转");
            directionId = R.mipmap.direction_yz;
            break;
            ······
        default:
            break;
    }
}

代码说明:获取的导航主动作编码与主动作对应关系为:0x8(继续直行)、0x1(左转)0x2(右转)、0x9(靠左行驶)、0xA(靠右行驶)、0x7(左转调头)、0x0B(进入环岛)、0x0C(离开环岛)。室内专用导航主动作编码:0x10(沿楼梯向上)、0x11(沿楼梯向下)、0x12(沿电梯向上)、0x13(沿电梯向下)、0x14(进入建筑物)、0x15(离开建筑物)。

获取导航动作信息后,需要将此信息通过界面展示给用户,一般通过图片形式展现当前导航动作,能够非常直观地告知用户该往什么方向行驶。要实现此效果,通常在布局设计时添加一个ImageView图像视图控件,在获取导航动作信息之后,根据对应关系为ImageView动态设置图片。

导航动作.png

(3)运动目标的当前位置信息

运动目标的当前位置信息:导航过程中另外一个很重要的信息是行人或者车辆当前的位置,一般通过在地图上绘制图标GraphicImage来表示。由于导航是一个动态变化的过程,运动物体的位置、前进的方向在导航过程中是不断变化的,因此需要动态变更其位置点与行进角度。

//导航指引信息回调函数
@Override
public void onRouteNaviInfo(RouteNaviInfo routenaviinfo, boolean arg1) {
    //获取当前位置点
    Dot currentDot = routenaviinfo.getPosition();
    //定位目标位置推算后的角度,与Y轴正方向的夹角,范围为[0,360],逆时针为正
    float angle = routenaviinfo.angle;
    //地图的旋转角度,单位度,逆时针为正
    float angle = 360 - angle;
    /*
    动态修改定位图标的位置
    说明:GraphicImage不能在监听回调中创建,避免多次进入,多次创建。需在导航之前就创建对象,在此监听中只需修改其位置即可。在此只是为展示完整的实现代码。
     */
    //开始导航前在地图上添加移动目标,作为导航目标的位置
    GraphicImage locationGraphicImage = new GraphicImage();
    //创建位图对象,并设置为GraphicImage
    Bitmap locationBmp = BitmapFactory.decodeResource(getResources(), R.mipmap.navigation_nogps);
    locationGraphicImage.setImage(locationBmp);
    //动态更新图像的位置,动态地展示导航运动过程
    locationGraphicImage.setPoint(currentDot);
    locationGraphicImage.setAnchorPoint(new PointF(0.5f, 0.5f));    //设置锚点
    mapView.getGraphicsOverlay().addGraphic(locationGraphicImage);  //添加到覆盖物图层中显示
    mapView.refresh();                                              //地图刷新
    /*
    动态修改地图的位置:将导航过程中当前位置设置为地图中心,并且旋转地图使得导航视角跟随车头,当然也可以不修改地图的位置、角度
     */
    MapPosition mapPositon = new MapPosition(currentDot, mapView.getResolution(), currentDot, angle, mapView.getSlopeAngle());          //地图位置(中心点,分辨率,旋转中心,旋转角,倾斜角)
    mapView.updatePosition(mapPositon, true);                     //更新位置
}

运动目标的地图位置显示效果如下图所示,可以根据路径的方向来改变地图的旋转角度,使导航视角跟随车头,与行人或者车辆真实情况中的朝向一致,营造一定程度的沉浸感。

导航运行目标.png

(4)路口放大图

路口放大图:将路口的分支情况以图片的形式放大展示,特别针对于复杂情况的路口,如典型的环岛,通过路口放大图,可以更加清晰地指引用户该往哪个方向行进。路口放大图的显示,需要自定义一个ImageView控件,通过SDK中的代码获取Bitmap位图,然后赋给图像视图控件,将当前路口放大图展示在界面中。

//导航指引信息回调函数
@Override
public void onRouteNaviInfo(RouteNaviInfo routenaviinfo, boolean arg1) {
    //路口放大图背景ID
    int mCrossBackID = routenaviinfo.crossBackID;
    if (getCrossImage(mCrossBackID) != null) {
        //如果获取的路口放大图Bitmap不为空,则赋给图像视图控件对象GraphicImage(说明:UI操作需在主线程执行,在此省略)
        crossGuideImgview.setImageBitmap(crossBitmap);
    }
}

//获取路口放大图Bitmap的自定义方法
private Bitmap getCrossImage(int crossBackID) {
    //位图
    Bitmap bitmap = null;
    //如果地图视图对象、路线对象有一者为空,则无法创建路口放大图
    if (mRoute == null || mapView == null) {
        return null;
    }
    //如果背景ID小于0,不存在放大图;如果背景ID大于减2后的路段总数,则不构建放大图
    if (crossBackID < 0 || crossBackID > mRoute.getStepCount() - 2) {
        return null;
    }
    //根据ID获取当前路段、下一路段、下下路段
    DriveWalkSegment driveWalkSegment = (DriveWalkSegment) mRoute.getStep(crossBackID);
    DriveWalkSegment driveWalkSegmentNext = (DriveWalkSegment) mRoute.getStep(crossBackID + 1);
    DriveWalkSegment driveWalkSegmentNextNext = null;
    //获取路段的节点数组
    Dot[] curSegDots = driveWalkSegment.getShapes();
    Dot[] nextSegDots = driveWalkSegmentNext.getShapes();
    Dot[] nextNextSegDots = null;
    //判断路段是否为环岛
    if (!driveWalkSegmentNext.getForm().contains("环岛")) {  //普通路口
        //根据地图视图大小构建位图,像素格式必须为ARGB_8888
        bitmap = Bitmap.createBitmap(mapView.getWidth(), mapView.getHeight() / 3, Bitmap.Config.ARGB_8888);
        //利用MapView根据传入的参数生成对应路口的放大图
        if (mMapView.getCrossBitmap(curSegDots, nextSegDots, nextNextSegDots, bitmap) == 1) {
            return bitmap;
        }
    } else {  //如果为环岛,需特殊处理
        //如果背景ID大于路径的路段数减3,则不构建放大图
        if (crossBackID > mRoute.getStepCount() - 3) {
            return null;
        }
        //获取当前路段的下下条路段,以及路段的点数组
        driveWalkSegmentNextNext = (DriveWalkSegment) mRoute.getStep(crossBackID + 2);
        nextNextSegDots = driveWalkSegmentNextNext.getShapes();
        //根据地图视图大小构建位图,像素格式必须为ARGB_8888
        bitmap = Bitmap.createBitmap(mapView.getWidth(), mapView.getHeight() / 3, Bitmap.Config.ARGB_8888);
        //利用MapView根据传入的参数生成对应路口的放大图
        if (mMapView.getCrossBitmap(curSegDots, nextSegDots, nextNextSegDots, bitmap) == 1) {
            return bitmap;
        }
    }
    return bitmap;
}

代码说明:路口放大图主要分为普通路口、环岛路口两种,对于环岛需要特殊处理。路口放大图的生成,需要使用MapView地图控件的getCrossBitmap方法实现。例如,普通路口放大图效果如下图所示。

路口放大图.png